/*
    Lontium LT8911EXB LVDS to EDP driver

    Copyright  (C)  2016 - 2017 Topband. Ltd.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be a reference
    to you, when you are integrating the Lontium's LT8911EXB IC into your system,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.

    Notes:
    1. IIC address of LT8911EXB:
     A) If LT8911EXBEXB's number 31 (S_ADR) is low, the I2C address of LT8911EXBEXB is 0x52,
        and bit0 is the read-write mark bit.
        If it is a Linux system, the I2C address of LT8911EXBEXB is 0x29, and bit7 is
        the read and write flag bit.
     B) If LT8911EXBEXB's number 31 (S_ADR) is high, then LT8911EXBEXB's I2C address is 0x5a,
        and bit0 is the read-write mark bit.
        If it is a Linux system, the I2C address of LT8911EXBEXB is 0x2d, and bit7 is
        the read and write flag bit.
    2. The IIC rate should not exceed 100KHz.
    3. To ensure that MIPI signal is given to LT8911EXBEXB, then initialize LT8911EXBEXB.
    4. The front-end master control GPIO is required to reply to LT8911EXBEXB. Before
      the register, the LT8911EXBEXB is reset.
      Use GPIO to lower LT8911EXBEXB's reset foot 100ms, then pull up and maintain 100ms.
    5. LT8911EXBEXB MIPI input signal requirements:
     A) LVDS
     B) Video mode
     C) non-burst mode (continue mode) - (CLK of MIPI is continuous).
     D) sync event

    Author: shenhaibo
    Version: 1.1.0
    Release Date: 2019/3/6
*/

#include <linux/irq.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/input/mt.h>
#include <linux/slab.h>
#include "lt8911exb.h"

/*******************************************************
    Function:
    Write data to the i2c slave device.
    Input:
    client: i2c device.
    buf[0]: write start address.
    buf[1~len-1]: data buffer
    len: LT8911EXB_ADDR_LENGTH + write bytes count
    Output:
    numbers of i2c_msgs to transfer:
        0: succeed, otherwise: failed
 *********************************************************/
int lt8911exb_i2c_write(struct i2c_client *client, u8 *buf, int len)
{
    unsigned int pos = 0, transfer_length = 0;
    u8 address = buf[0];
    unsigned char put_buf[64];
    int retry, ret = 0;
    struct i2c_msg msg = {
        .addr = client->addr,
        .flags = !I2C_M_RD,
    };

    if(likely(len < sizeof(put_buf))) {
        /* code optimize,use stack memory*/
        msg.buf = &put_buf[0];
    } else {
        msg.buf = kmalloc(len > I2C_MAX_TRANSFER_SIZE
                          ? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);

        if(!msg.buf)
            return -ENOMEM;
    }

    len -= LT8911EXB_ADDR_LENGTH;

    while(pos != len) {
        if(unlikely(len - pos > I2C_MAX_TRANSFER_SIZE - LT8911EXB_ADDR_LENGTH))
            transfer_length = I2C_MAX_TRANSFER_SIZE - LT8911EXB_ADDR_LENGTH;
        else
            transfer_length = len - pos;

        msg.buf[0] = address;
        msg.len = transfer_length + LT8911EXB_ADDR_LENGTH;
        memcpy(&msg.buf[LT8911EXB_ADDR_LENGTH], &buf[LT8911EXB_ADDR_LENGTH + pos], transfer_length);

        for(retry = 0; retry < RETRY_MAX_TIMES; retry++) {
            if(likely(i2c_transfer(client->adapter, &msg, 1) == 1)) {
                pos += transfer_length;
                address += transfer_length;
                break;
            }

            dev_info(&client->dev, "I2C write retry[%d]\n", retry + 1);
            udelay(2000);
        }

        if(unlikely(retry == RETRY_MAX_TIMES)) {
            dev_err(&client->dev,
                    "I2c write failed,dev:%02x,reg:%02x,size:%u\n",
                    client->addr, address, len);
            ret = -EAGAIN;
            goto write_exit;
        }
    }

write_exit:

    if(len + LT8911EXB_ADDR_LENGTH >= sizeof(put_buf))
        kfree(msg.buf);

    return ret;
}

/*******************************************************
    Function:
    Read data from the i2c slave device.
    Input:
    client: i2c device.
    buf[0]: read start address.
    buf[1~len-1]: data buffer
    len: LT8911EXB_ADDR_LENGTH + read bytes count
    Output:
    numbers of i2c_msgs to transfer:
        0: succeed, otherwise: failed
 *********************************************************/
int lt8911exb_i2c_read(struct i2c_client *client, u8 *buf, int len)
{
    unsigned int transfer_length = 0;
    unsigned int pos = 0;
    u8 address = buf[0];
    unsigned char get_buf[64], addr_buf[2];
    int retry, ret = 0;
    struct i2c_msg msgs[] = {
        {
            .addr = client->addr,
            .flags = !I2C_M_RD,
            .buf = &addr_buf[0],
            .len = LT8911EXB_ADDR_LENGTH,
        }, {
            .addr = client->addr,
            .flags = I2C_M_RD,
        }
    };

    len -= LT8911EXB_ADDR_LENGTH;

    if(likely(len < sizeof(get_buf))) {
        /* code optimize, use stack memory */
        msgs[1].buf = &get_buf[0];
    } else {
        msgs[1].buf = kzalloc(len > I2C_MAX_TRANSFER_SIZE
                              ? I2C_MAX_TRANSFER_SIZE : len, GFP_KERNEL);

        if(!msgs[1].buf)
            return -ENOMEM;
    }

    while(pos != len) {
        if(unlikely(len - pos > I2C_MAX_TRANSFER_SIZE))
            transfer_length = I2C_MAX_TRANSFER_SIZE;
        else
            transfer_length = len - pos;

        msgs[0].buf[0] = address;
        msgs[1].len = transfer_length;

        for(retry = 0; retry < RETRY_MAX_TIMES; retry++) {
            if(likely(i2c_transfer(client->adapter, msgs, 2) == 2)) {
                memcpy(&buf[LT8911EXB_ADDR_LENGTH + pos], msgs[1].buf, transfer_length);
                pos += transfer_length;
                address += transfer_length;
                break;
            }

            dev_info(&client->dev, "I2c read retry[%d]:0x%x\n",
                     retry + 1, address);
            udelay(2000);
        }

        if(unlikely(retry == RETRY_MAX_TIMES)) {
            dev_err(&client->dev,
                    "I2c read failed,dev:%02x,reg:%02x,size:%u\n",
                    client->addr, address, len);
            ret = -EAGAIN;
            goto read_exit;
        }
    }

read_exit:

    if(len >= sizeof(get_buf))
        kfree(msgs[1].buf);

    return ret;
}

int lt8911exb_write(struct i2c_client *client, u8 addr, u8 data)
{
    u8 buf[2] = {addr, data};
    int ret = -1;

    ret = lt8911exb_i2c_write(client, buf, 2);

    return ret;
}

u8 lt8911exb_read(struct i2c_client *client, u8 addr)
{
    u8 buf[2] = {addr};
    int ret = -1;

    ret = lt8911exb_i2c_read(client, buf, 2);

    if(ret == 0) {
        return buf[1];
    } else {
        return 0;
    }
}

void lt8911exb_reset_guitar(struct i2c_client *client)
{
    struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);

    dev_info(&client->dev, "Guitar reset");

    if(!gpio_is_valid(lt8911exb->rst_gpio)) {
        dev_warn(&client->dev, "reset failed no valid reset gpio");
        return;
    }

    gpio_direction_output(lt8911exb->rst_gpio, 0);
    mdelay(110);
    //usleep_range(100*1000, 150*1000); //150ms
    gpio_direction_output(lt8911exb->rst_gpio, 1);
    mdelay(120);
    //usleep_range(100*1000, 150*1000); //150ms
    gpio_direction_input(lt8911exb->rst_gpio);
}


static int lt8911exb_parse_dt(struct device *dev,
                              struct lt8911exb_data *pdata)
{
    int ret = 0;
    struct device_node *np = dev->of_node;

    pdata->pwr_gpio = of_get_named_gpio(np, "power-gpio", 0);

    if(!gpio_is_valid(pdata->pwr_gpio)) {
        dev_err(dev, "No valid pwr gpio");
        return -1;
    }

    pdata->rst_gpio = of_get_named_gpio(np, "reset-gpio", 0);

    if(!gpio_is_valid(pdata->rst_gpio)) {
        dev_err(dev, "No valid rst gpio");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,test", &pdata->test);
    if (ret) {
        dev_err(dev, "Parse test failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,mipi_lane", &pdata->mipi_lane);
    if (ret) {
        dev_err(dev, "Parse mipi_lane failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,lane_cnt", &pdata->lane_cnt);
    if (ret) {
        dev_err(dev, "Parse lane_cnt failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,color", &pdata->color);
    if (ret) {
        dev_err(dev, "Parse color failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,lane_cnt", &pdata->lane_cnt);
    if (ret) {
        dev_err(dev, "Parse lane_cnt failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,hact", &pdata->hact);
    if (ret) {
        dev_err(dev, "Parse hact failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,hact", &pdata->hact);
    if (ret) {
        dev_err(dev, "Parse hact failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,vact", &pdata->vact);
    if (ret) {
        dev_err(dev, "Parse vact failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,hbp", &pdata->hbp);
    if (ret) {
        dev_err(dev, "Parse hbp failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,hfp", &pdata->hfp);
    if (ret) {
        dev_err(dev, "Parse hfp failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,hs", &pdata->hs);
    if (ret) {
        dev_err(dev, "Parse hs failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,vbp", &pdata->vbp);
    if (ret) {
        dev_err(dev, "Parse vbp failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,vfp", &pdata->vfp);
    if (ret) {
        dev_err(dev, "Parse vfp failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,vs", &pdata->vs);
    if (ret) {
        dev_err(dev, "Parse vs failed");
        return -1;
    }

    ret = of_property_read_u32(np, "lontium,pclk", &pdata->pclk);
    if (ret) {
        dev_err(dev, "Parse pclk failed");
        return -1;
    } else {
        pdata->pclk = pdata->pclk/10000; // 10khz
    }

    pdata->htotal = pdata->hact + pdata->hbp + pdata->hfp + pdata->hs;
    pdata->vtotal = pdata->vact + pdata->vbp + pdata->vfp + pdata->vs;

    return 0;
}

static int lt8911exb_request_io_port(struct lt8911exb_data *lt8911exb)
{
    int ret = 0;

    if(gpio_is_valid(lt8911exb->pwr_gpio)) {
        ret = gpio_request(lt8911exb->pwr_gpio, "lt8911exb_pwr");

        if(ret < 0) {
            dev_err(&lt8911exb->client->dev,
                    "Failed to request GPIO:%d, ERRNO:%d\n",
                    (s32)lt8911exb->pwr_gpio, ret);
            return -ENODEV;
        }

        gpio_direction_input(lt8911exb->pwr_gpio);
        dev_info(&lt8911exb->client->dev, "Success request pwr-gpio\n");
    }

    if(gpio_is_valid(lt8911exb->rst_gpio)) {
        ret = gpio_request(lt8911exb->rst_gpio, "lt8911exb_rst");

        if(ret < 0) {
            dev_err(&lt8911exb->client->dev,
                    "Failed to request GPIO:%d, ERRNO:%d\n",
                    (s32)lt8911exb->rst_gpio, ret);

            if(gpio_is_valid(lt8911exb->pwr_gpio))
                gpio_free(lt8911exb->pwr_gpio);

            return -ENODEV;
        }

        gpio_direction_input(lt8911exb->rst_gpio);
        dev_info(&lt8911exb->client->dev,  "Success request rst-gpio\n");
    }

    return 0;
}

static int lt8911exb_i2c_test(struct i2c_client *client)
{
    u8 retry = 0;
    u8 chip_id_h = 0, chip_id_m = 0, chip_id_l = 0;
    int ret = -EAGAIN;

    while(retry++ < 3) {
        ret = lt8911exb_write(client, 0xff, 0x81);

        if(ret < 0) {
            dev_err(&client->dev, "LT8911EXB i2c test write addr:0xff failed\n");
            continue;
        }

        ret = lt8911exb_write(client, 0x08, 0x7f);

        if(ret < 0) {
            dev_err(&client->dev, "LT8911EXB i2c test write addr:0x08 failed\n");
            continue;
        }

        chip_id_l = lt8911exb_read(client, 0x00);
        chip_id_m = lt8911exb_read(client, 0x01);
        chip_id_h = lt8911exb_read(client, 0x02);
        // LT8911EXB i2c test success chipid: 0xe0517
        dev_info(&client->dev, "LT8911EXB i2c test success chipid: 0x%x%x%x\n", chip_id_h, chip_id_m, chip_id_l);

//        if (chip_id_h == 0 && chip_id_l == 0) {
//            dev_err(&client->dev, "LT8911EXB i2c test failed time %d\n", retry);
//            continue;
//        }

        ret = 0;
        break;
    }

    return ret;
}

void lt8911exb_dpcd_write(struct i2c_client *client, u32 address, u8 Data)
{
    u8 address_h = 0x0f & (address >> 16);
    u8 address_m = 0xff & (address >> 8);
    u8 address_l = 0xff & address;
    u8 ret = 0x00;

    lt8911exb_write(client, 0xff, 0xa6);
    lt8911exb_write(client, 0x2b, (0x80|address_h));
    lt8911exb_write(client, 0x2b,address_m); //addr[15:8]
    lt8911exb_write(client, 0x2b,address_l); //addr[7:0]
    lt8911exb_write(client, 0x2b,0x00); //data lenth
    lt8911exb_write(client, 0x2b,Data); //data
    lt8911exb_write(client, 0x2c,0x00); //start Aux read edid

    mdelay(20);
    ret = lt8911exb_read(client, 0x25);

    if((ret&0x0f)== 0x0c) {
        return;
    }
}

u8 lt8911exb_dpcd_read(struct i2c_client *client, u32 address)
{
    //u8 read_cnt = 0x03;
    u8 dpcd_value = 0x00;
    u8 address_h = 0x0f & (address >> 16);
    u8 address_m = 0xff & (address >> 8);
    u8 address_l = 0xff & address;

    u8	reg;

    lt8911exb_write(client, 0xff, 0xac );                  // register bank
	lt8911exb_write(client, 0x00, 0x20 );                  //Soft Link train
	lt8911exb_write(client, 0xff, 0xa6 );
	lt8911exb_write(client, 0x2a, 0x01 );

	lt8911exb_write(client, 0xff, 0xa6 );                  // register bank
	lt8911exb_write(client, 0x2b, ( 0x90 | address_h ) );   //CMD
	lt8911exb_write(client, 0x2b, address_m );              //addr[15:8]
	lt8911exb_write(client, 0x2b, address_l );              //addr[7:0]
	lt8911exb_write(client, 0x2b, 0x00 );                  //data lenth
	lt8911exb_write(client, 0x2c, 0x00 );                  //start Aux read edid

	mdelay(50);                                        //more than 10ms
	reg = lt8911exb_read(client, 0x25 );
	if( ( reg & 0x0f ) == 0x0c )
	{
		if( lt8911exb_read(client, 0x39 ) == 0x22 )
		{
			lt8911exb_read(client, 0x2b );
			dpcd_value = lt8911exb_read(client, 0x2b );
		}
	}else
	{
		// AUX reset
		lt8911exb_write(client, 0xff, 0x81 ); // change bank
		lt8911exb_write(client, 0x07, 0xfe );
		lt8911exb_write(client, 0x07, 0xff );
		lt8911exb_write(client, 0x0a, 0xfc );
		lt8911exb_write(client, 0x0a, 0xfe );
	}


    // lt8911exb_write(client, 0xff, 0x80);
    // lt8911exb_write(client, 0x62, 0xbd);
    // lt8911exb_write(client, 0x62, 0xbf); //ECO(AUX reset)

    // lt8911exb_write(client, 0x36, 0x00);
    // lt8911exb_write(client, 0x30, 0x8f); //0x91
    // lt8911exb_write(client, 0x33, address_l);
    // lt8911exb_write(client, 0x34, address_m);
    // lt8911exb_write(client, 0x35, address_h);
    // lt8911exb_write(client, 0x36, 0x20);

    // mdelay(2); //The necessary

    // if(lt8911exb_read(client, 0x39) == 0x01) {
    //     dpcd_value = lt8911exb_read(client, 0x38);
    // } else {
    //     while((lt8911exb_read(client, 0x39) != 0x01) && (read_cnt > 0)) {
    //         lt8911exb_write(client, 0x36, 0x00);
    //         lt8911exb_write(client, 0x36, 0x20);
    //         read_cnt--;
    //         mdelay(2);
    //     }

    //     if(lt8911exb_read(client, 0x39) == 0x01) {
    //         dpcd_value = lt8911exb_read(client, 0x38);
    //     }
    // }

    return dpcd_value;
}

void lt8911exb_mipi_video_timing(struct i2c_client *client)
{
    u8 dessc_m;
    struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
    
    // lt8911exb_write(client, 0xff, 0xd0);
    // lt8911exb_write(client, 0x0d, (u8)(lt8911exb->vtotal / 256));
    // lt8911exb_write(client, 0x0e, (u8)(lt8911exb->vtotal % 256));    //vtotal
    // lt8911exb_write(client, 0x0f, (u8)(lt8911exb->vact / 256));
    // lt8911exb_write(client, 0x10, (u8)(lt8911exb->vact % 256));      //vactive
    // lt8911exb_write(client, 0x11, (u8)(lt8911exb->htotal / 256));
    // lt8911exb_write(client, 0x12, (u8)(lt8911exb->htotal % 256));    //htotal
    // lt8911exb_write(client, 0x13, (u8)(lt8911exb->hact / 256));
    // lt8911exb_write(client, 0x14, (u8)(lt8911exb->hact % 256));      //hactive
    // lt8911exb_write(client, 0x15, (u8)(lt8911exb->vs % 256));        //vsa
    // lt8911exb_write(client, 0x16, (u8)(lt8911exb->hs % 256));        //hsa
    // lt8911exb_write(client, 0x17, (u8)(lt8911exb->vfp / 256));
    // lt8911exb_write(client, 0x18, (u8)(lt8911exb->vfp % 256));       //vfp
    // lt8911exb_write(client, 0x19, (u8)(lt8911exb->hfp / 256));
    // lt8911exb_write(client, 0x1a, (u8)(lt8911exb->hfp % 256));       //hfp

    // LVDS de only mode to regenerate h/v sync.
    lt8911exb_write(client, 0xff, 0xd8);// register bank
    lt8911exb_write(client, 0x20, (u8)(lt8911exb->hfp / 256));
    lt8911exb_write(client, 0x21, (u8)(lt8911exb->hfp % 256));       //hfp

    lt8911exb_write(client, 0x22, (u8)(lt8911exb->hs / 256));
    lt8911exb_write(client, 0x23, (u8)(lt8911exb->hs % 256));        //hsa

    lt8911exb_write(client, 0x24, (u8)(lt8911exb->htotal / 256));
    lt8911exb_write(client, 0x25, (u8)(lt8911exb->htotal % 256));    //htotal

    lt8911exb_write(client, 0x26, (u8)(lt8911exb->vfp % 256));
    lt8911exb_write(client, 0x27, (u8)(lt8911exb->vs % 256));       //vfp

    dessc_m = ( (lt8911exb->pclk/10000) * 4 ) / ( 25 * 100 );

    lt8911exb_write(client, 0xff, 0x85);// register bank
    lt8911exb_write(client, 0xaa, dessc_m);
}

void lt8911exb_edp_video_cfg(struct i2c_client *client)
{
    u8 dessc_m;
    struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
    
    lt8911exb_write(client, 0xff, 0xa8);

    lt8911exb_write(client, 0x2d, 0x04); // MSA from register
    lt8911exb_write(client, 0x05, (u8)(lt8911exb->htotal / 256));
    lt8911exb_write(client, 0x06, (u8)(lt8911exb->htotal % 256));
    lt8911exb_write(client, 0x07, (u8)((lt8911exb->hs + lt8911exb->hbp) / 256));
    lt8911exb_write(client, 0x08, (u8)((lt8911exb->hs + lt8911exb->hbp) % 256));
    lt8911exb_write(client, 0x09, (u8)(lt8911exb->hs / 256));
    lt8911exb_write(client, 0x0a, (u8)(lt8911exb->hs % 256));
    lt8911exb_write(client, 0x0b, (u8)(lt8911exb->hact / 256));
    lt8911exb_write(client, 0x0c, (u8)(lt8911exb->hact % 256));
    lt8911exb_write(client, 0x0d, (u8)(lt8911exb->vtotal / 256));
    lt8911exb_write(client, 0x0e, (u8)(lt8911exb->vtotal % 256));
    lt8911exb_write(client, 0x11, (u8)((lt8911exb->vs + lt8911exb->vbp) / 256));
    lt8911exb_write(client, 0x12, (u8)((lt8911exb->vs + lt8911exb->vbp) % 256));
    lt8911exb_write(client, 0x14, (u8)(lt8911exb->vs % 256));
    lt8911exb_write(client, 0x15, (u8)(lt8911exb->vact / 256));
    lt8911exb_write(client, 0x16, (u8)(lt8911exb->vact % 256));

    // LVDS de only mode to regenerate h/v sync.
    lt8911exb_write(client, 0xff, 0xd8);// register bank
    lt8911exb_write(client, 0x20, (u8)(lt8911exb->hfp / 256));
    lt8911exb_write(client, 0x21, (u8)(lt8911exb->hfp % 256));       //hfp

    lt8911exb_write(client, 0x22, (u8)(lt8911exb->hs / 256));
    lt8911exb_write(client, 0x23, (u8)(lt8911exb->hs % 256));        //hsa

    lt8911exb_write(client, 0x24, (u8)(lt8911exb->htotal / 256));
    lt8911exb_write(client, 0x25, (u8)(lt8911exb->htotal % 256));    //htotal

    lt8911exb_write(client, 0x26, (u8)(lt8911exb->vfp % 256));
    lt8911exb_write(client, 0x27, (u8)(lt8911exb->vs % 256));       //vfp

    dessc_m = ( (lt8911exb->pclk/10000) * 4 ) / ( 25 * 100 );

    lt8911exb_write(client, 0xff, 0x85);// register bank
    lt8911exb_write(client, 0xaa, dessc_m);
}

void lt8911exb_setup(struct i2c_client *client)
{
    u8 i;

    //struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);

    LVDS_Port = _LVDS_Port_;

    /* init */
    lt8911exb_write(client, 0xff, 0x81);
    lt8911exb_write(client, 0x49, 0xff); //enable 0x87xx

 	/* Txpll 2.7G*/
#ifdef _eDP_2G7_
	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x19, 0x31 );
//	lt8911exb_write(client, 0x1a, 0x36 );  // sync m
	lt8911exb_write(client, 0x1a, 0x1b );
	lt8911exb_write(client, 0x1b, 0x00 );  // sync_k [7:0]
	lt8911exb_write(client, 0x1c, 0x00 );  // sync_k [13:8]

	// txpll Analog
	lt8911exb_write(client, 0xff, 0x82 );  // register bank
	lt8911exb_write(client, 0x09, 0x00 ); // div hardware mode, for ssc.
//	lt8911exb_write(client, 0x01, 0x18 );// default : 0x18
	lt8911exb_write(client, 0x02, 0x42 );
	lt8911exb_write(client, 0x03, 0x00 );  // txpll en = 0
	lt8911exb_write(client, 0x03, 0x01 );  // txpll en = 1
//	lt8911exb_write(client, 0x04, 0x3a );// default : 0x3A
	lt8911exb_write(client, 0x0a, 0x1b );
	lt8911exb_write(client, 0x04, 0x2a );

	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x0c, 0x10 );  // cal en = 0

	lt8911exb_write(client, 0xff, 0x81 );  // register bank
	lt8911exb_write(client, 0x09, 0xfc );
	lt8911exb_write(client, 0x09, 0xfd );

	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x0c, 0x11 );  // cal en = 1

	// ssc
	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x13, 0x83 );
	lt8911exb_write(client, 0x14, 0x41 );
	lt8911exb_write(client, 0x16, 0x0a );
	lt8911exb_write(client, 0x18, 0x0a );
	lt8911exb_write(client, 0x19, 0x33 );
#endif   

#ifdef _eDP_1G62_
	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x19, 0x31 );
	lt8911exb_write(client, 0x1a, 0x20 );  // sync m
	lt8911exb_write(client, 0x1b, 0x19 );  // sync_k [7:0]
	lt8911exb_write(client, 0x1c, 0x99 );  // sync_k [13:8]

	// txpll Analog
	lt8911exb_write(client, 0xff, 0x82 );  // register bank
	lt8911exb_write(client, 0x09, 0x00 );// div hardware mode, for ssc.
	//	lt8911exb_write(client, 0x01, 0x18 );// default : 0x18
	lt8911exb_write(client, 0x02, 0x42 );
	lt8911exb_write(client, 0x03, 0x00 );  // txpll en = 0
	lt8911exb_write(client, 0x03, 0x01 );  // txpll en = 1
	//	lt8911exb_write(client, 0x04, 0x3a );// default : 0x3A

	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x0c, 0x10 );  // cal en = 0

	lt8911exb_write(client, 0xff, 0x81 );  // register bank
	lt8911exb_write(client, 0x09, 0xfc );
	lt8911exb_write(client, 0x09, 0xfd );

	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x0c, 0x11 );  // cal en = 1

	//ssc
	lt8911exb_write(client, 0xff, 0x87 );  // register bank
	lt8911exb_write(client, 0x13, 0x83 );
	lt8911exb_write(client, 0x14, 0x41 );
	lt8911exb_write(client, 0x16, 0x0a );
	lt8911exb_write(client, 0x18, 0x0a );
	lt8911exb_write(client, 0x19, 0x33 );
#endif
    
    lt8911exb_write(client, 0xff, 0x87);

    for(i = 0; i < 5; i++) { //Check Tx PLL
        mdelay(10);

        if(lt8911exb_read(client, 0x37) & 0x02) {
            dev_info(&client->dev,  "LT8911 tx pll locked");
            lt8911exb_write(client, 0xff, 0x87);
            lt8911exb_write(client, 0x1a, 0x36);
            lt8911exb_write(client, 0xff, 0x82);
            lt8911exb_write(client, 0x0a, 0x36);
            lt8911exb_write(client, 0x04, 0x3a);
            break;
        } else {
            dev_info(&client->dev,  "LT8911 tx pll unlocked");
            lt8911exb_write(client, 0xff, 0x81);
            lt8911exb_write(client, 0x09, 0xfc);
            lt8911exb_write(client, 0x09, 0xfd);
            lt8911exb_write(client, 0xff, 0x87);
            lt8911exb_write(client, 0x0c, 0x10);
            lt8911exb_write(client, 0x0c, 0x11);
        }
    }

	/* lvds Rx analog */
	lt8911exb_write(client, 0xff, 0x82 );      // register bank
	lt8911exb_write(client, 0x3e, 0xa1 );

	//default setting
	/* lvds Rx pll */
	if( LVDS_Port == _1_Port_ )                 // 1port lvds
	{
		lt8911exb_write(client, 0xff, 0x82 );  // register bank
		lt8911exb_write(client, 0x63, 0x84 );
		lt8911exb_write(client, 0x67, 0x40 );
	}

	/* dessc/pcr  pll- analog */
#ifdef _EDP_Pattern_
	lt8911exb_write(client, 0xff, 0xd0 );      // register bank
	lt8911exb_write(client, 0x08, 0x08 );

	lt8911exb_write(client, 0xff, 0x82 );      // register bank
	lt8911exb_write(client, 0x6e, 0x81 );
	lt8911exb_write(client, 0x6a, 0x40 );      //0x50:Pattern; 0x10:mipi video

	/* dessc pll digital */
	lt8911exb_write(client, 0xff, 0x85 );      // register bank
	lt8911exb_write(client, 0xa9, 0x31 );      //bit[0] = 1 : select software MK.
	lt8911exb_write(client, 0xae, 0x01 );      //load MK value
	lt8911exb_write(client, 0xae, 0x11 );

#endif

	/* digital top */
	lt8911exb_write(client, 0xff, 0x85 );      // register bank
#ifdef _8_to_6bit_Dither_En
	lt8911exb_write(client, 0xb0, 0xd0 );
#else
	lt8911exb_write(client, 0xb0, 0x00 );
#endif

	/* lvds digital */
	lt8911exb_write(client, 0xff, 0x85 );  // register bank
	lt8911exb_write(client, 0xc3, 0x20 );  //portB LVDS clk from portA

	lt8911exb_write(client, 0xff, 0x81 );  // register bank
	lt8911exb_write(client, 0x05, 0xae );
	lt8911exb_write(client, 0x05, 0xfe );

	// AUX reset
	lt8911exb_write(client, 0xff, 0x81 );  // register bank
	lt8911exb_write(client, 0x07, 0xfe );
	lt8911exb_write(client, 0x07, 0xff );
	lt8911exb_write(client, 0x0a, 0xfc );
	lt8911exb_write(client, 0x0a, 0xfe );

	lt8911exb_write(client, 0xff, 0xd8 );  // register bank

//	if(_LVDS_Port_ == _1_Port_) // 1port lvds
//	{
	lt8911exb_write(client, 0x10, _LVDS_Port_ );
//	}

//	#ifdef lvds_format_JEIDA
	lt8911exb_write(client, 0x11, _LVDS_RX_ );
//	#endif

// #ifdef lvds_sync_de_only
	lt8911exb_write(client, 0x1f, _LVDS_Mode_ );
// #endif

	// eDP out swap config:
	lt8911exb_write(client, 0xff, 0xac ); // Change Reg bank
	lt8911exb_write(client, 0x15, _eDP_data_sequence_ ); // edp data swap
	lt8911exb_write(client, 0x16, _eDP_data_PN_); // eDP P / N swap

	/* tx phy */
	lt8911exb_write(client, 0xff, 0x82 );  //register bank
	lt8911exb_write(client, 0x11, 0x00 );
	lt8911exb_write(client, 0x13, 0x10 );
	lt8911exb_write(client, 0x14, 0x0c );
	lt8911exb_write(client, 0x14, 0x08 );
	lt8911exb_write(client, 0x13, 0x20 );

	lt8911exb_write(client, 0xff, 0x82 );  //register bank
	lt8911exb_write(client, 0x0e, 0x35 );

#if( _eDP_Lane_ == 2) // 2 lane eDP
		{
		lt8911exb_write(client, 0x12, 0x33 );
		}
#elif( _eDP_Lane_ == 1) // 1 lane eDP
		{
		lt8911exb_write(client, 0x12, 0x11 );
		}
#elif( _eDP_Lane_ == 4) // 4 lane eDP
		{
		lt8911exb_write(client, 0x12, 0xff );
		}
#endif


//	lt8911exb_write(client, 0xff, 0x80 );//register bank
//	lt8911exb_write(client, 0x40, 0x22 );

	/*eDP Tx Digital */
	lt8911exb_write(client, 0xff, 0xa8 );  //register bank

#ifdef _EDP_Pattern_
	lt8911exb_write(client, 0x24, 0x50 );  // bit2 ~ bit 0 : test panttern image mode
	lt8911exb_write(client, 0x25, 0x70 );  // bit6 ~ bit 4 : test Pattern color

	lt8911exb_write(client, 0x27, 0x50 );  // 0x50:Pattern; 0x10:mipi video

//	lt8911exb_write(client, 0x2d, 0x00 ); //  pure color setting
//	lt8911exb_write(client, 0x2d, 0x84 ); // black color
//	lt8911exb_write(client, 0x2d, 0x88 ); //  block
#else
	lt8911exb_write(client, 0x27, 0x10 );  // 0x50:Pattern; 0x10:mipi video
#endif

#ifdef _6_bit_eDP_
	lt8911exb_write(client, 0x17, 0x00 );
	lt8911exb_write(client, 0x18, 0x00 );
#else
	// _8_bit_eDP_
	lt8911exb_write(client, 0x17, 0x10 );
	lt8911exb_write(client, 0x18, 0x20 );
#endif

	/* nvid */
	lt8911exb_write(client, 0xff, 0xa0 );  // register bank  //nvid = 0x080000;
	lt8911exb_write(client, 0x00, 0x00 );  // 0x00 / 0x08
	lt8911exb_write(client, 0x01, 0x80 );  // 0x80 / 0x00

#ifdef _INT_En_
	/* irq: lvds_clk_chg*/
	lt8911exb_write(client, 0xff, 0x85 );  // register bank
	lt8911exb_write(client, 0x41, 0x3c );

//	lt8911exb_write(client, 0x02, 0x7f );			// scr2     //mask bit7 vid_chk
//	lt8911exb_write(client, 0x03, 0x0b );			// cr3	   //mask: bit[7:6]
	lt8911exb_write(client, 0x02, 0x7f );  // mask: bit[7] vid_chk
	lt8911exb_write(client, 0x03, 0x3f );  // mask: bit[7:6]

	lt8911exb_write(client, 0x07, 0xff );
	lt8911exb_write(client, 0x08, 0xff );

//	lt8911exb_write(client, 0x07, 0x7f );
//	lt8911exb_write(client, 0x08, 0x0b );			// INt clear

	/* interrupt */
	lt8911exb_write(client, 0xff, 0x82 );          // register bank
	lt8911exb_write(client, 0x5f, 0x00 );          //enable IRQ
#endif
	/* gpio init */
	lt8911exb_write(client, 0xff, 0xd8 );          // register bank
	lt8911exb_write(client, 0x16, sync_source );   // lvds gpio test select: 0x02 port A, 0x03 portB, 0x01 lvds Rx

	lt8911exb_write(client, 0xff, 0x85 );          // register bank
	lt8911exb_write(client, 0x1b, 0x05 );          //gpio0: irq; gpio1: hpd; gpio2: test2; gpio3: test3; gpio4:test4
	lt8911exb_write(client, 0xc0, 0x00 );          //test2: de
	lt8911exb_write(client, 0xc1, 0x24 );          //test3: vs, test4：hs

	lt8911exb_write(client, 0xff, 0x82 );          // register bank
	lt8911exb_write(client, 0x5a, 0x01 );          //disable atest
	lt8911exb_write(client, 0x5b, 0xc0 );          //gpio0/1: oppen drain; gpio2/3 pp;

#ifdef _gpio_sync_output_
	lt8911exb_write(client, 0x60, 0x00 );          //gpio2/3: test mode
	lt8911exb_write(client, 0x61, 0x00 );          //gpio4: test mode
#else
	lt8911exb_write(client, 0x60, 0x14 );          //gpio2/3: not test mode
	lt8911exb_write(client, 0x61, 0x80 );          //gpio4: not test mode
#endif

}

/* mipi should be ready before configuring below video check setting*/
void lt8911exb_video_check(struct i2c_client *client)
{
//	u16 hfp, hs, hbp,  htotal, vfp, vs, vbp,  vtotal;
	u8 sync_polarity;

	lt8911exb_write(client, 0xff, 0x85 );      // register bank

	if( ScrambleMode )
	{
		lt8911exb_write(client, 0xa1, 0x81 );  //eDP scramble mode;
	}else
	{
		lt8911exb_write(client, 0xa1, 0x01 );  // DP scramble mode;
	}

	sync_polarity  = lt8911exb_read(client, 0x7a );
	v_sync		   = lt8911exb_read(client, 0x7b );

	h_sync = lt8911exb_read(client, 0x7c );
	h_sync = h_sync * 0x100 + lt8911exb_read(client, 0x7d );

	v_bp   = lt8911exb_read(client, 0x7e );
	v_fp   = lt8911exb_read(client, 0x7f );

	h_bp   = lt8911exb_read(client, 0x80 );
	h_bp   = h_bp * 0x100 + lt8911exb_read(client, 0x81 );

	h_fp   = lt8911exb_read(client, 0x82 );
	h_fp   = h_fp * 0x100 + lt8911exb_read(client, 0x83 );

	v_total	   = lt8911exb_read(client, 0x84 );
	v_total	   = v_total * 0x100 + lt8911exb_read(client, 0x85 );

	h_total	   = lt8911exb_read(client, 0x86 );
	h_total	   = h_total * 0x100 + lt8911exb_read(client, 0x87 );

	v_active   = lt8911exb_read(client, 0x88 );
	v_active   = v_active * 0x100 + lt8911exb_read(client, 0x89 );

	h_active   = lt8911exb_read(client, 0x8a );
	h_active   = h_active * 0x100 + lt8911exb_read(client, 0x8b );

    dev_info(&client->dev,  "video check: v_sync = %d", v_sync);
    dev_info(&client->dev,  "video check: h_sync = %d", h_sync);
    
    dev_info(&client->dev,  "video check: v_total = %d", v_total);
    dev_info(&client->dev,  "video check: h_total = %d", h_total);

    dev_info(&client->dev,  "video check: Vact = %d", v_active);
    dev_info(&client->dev,  "video check: Hact = %d", h_active);

#ifdef _uart_debug_

	printf( "\r\nsync_polarity = %x", sync_polarity );

	printf( "\r\nhfp, hs, hbp, hact, htotal = " );
	printf( h_fp );
	printf( h_sync );
	printf( h_bp );
	printf( h_active );
	printf( h_total );

	printf( "\r\nvfp, vs, vbp, vact, vtotal = " );
	printf( v_fp );
	printf( v_sync );
	printf( v_bp );
	printf( v_active );
	printf( v_total );
#endif

}

void lt8911exb_link_train(struct i2c_client *client)
{
    //struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);
    
	lt8911exb_write(client, 0xff, 0x81 );      // register bank
	lt8911exb_write(client, 0x06, 0xdf );
	mdelay(10);
	lt8911exb_write(client, 0x06, 0xff );

	lt8911exb_write(client, 0xff, 0x85 );      // register bank

//	lt8911exb_write(client, 0x17, 0xf0 );// EDP TX frame change disable.

//#ifdef _eDP_scramble_
	if( ScrambleMode )
	{
		lt8911exb_write(client, 0xa1, 0x81 );  // eDP scramble mode;

		/* Aux operater init */
		lt8911exb_write(client, 0xff, 0xac );  // register bank
		lt8911exb_write(client, 0x00, 0x20 );  //Soft Link train
		lt8911exb_write(client, 0xff, 0xa6 );
		lt8911exb_write(client, 0x2a, 0x01 );
#if( _eDP_Lane_ == 4) 
		lt8911exb_dpcd_write(client, 0x0101, 0x84 );
		mdelay(10);
#endif

		lt8911exb_dpcd_write(client, 0x010a, 0x01 );
		mdelay(10);
		lt8911exb_dpcd_write(client, 0x0102, 0x00 );
		mdelay(10);
		lt8911exb_dpcd_write(client, 0x010a, 0x01 );

		mdelay(200);
	}
//#else
	else
	{
		lt8911exb_write(client, 0xa1, 0x01 );  // DP scramble mode;
		
#if( _eDP_Lane_ == 4) 
		lt8911exb_write(client, 0xff, 0xac );
		lt8911exb_write(client, 0x00, 0x20 ); //Soft Link train
		lt8911exb_write(client, 0xff, 0xa6 );
		lt8911exb_write(client, 0x2a, 0x01 );

		lt8911exb_dpcd_write(client, 0x0101, 0x84 );
		mdelay(10);
#endif

		
	}
//#endif

	/* Aux setup */
	lt8911exb_write(client, 0xff, 0xac );      // register bank
	lt8911exb_write(client, 0x00, 0x60 );

	lt8911exb_write(client, 0xff, 0xa6 );      // register bank
	lt8911exb_write(client, 0x2a, 0x00 );

	lt8911exb_write(client, 0xff, 0x81 );      // register bank
	lt8911exb_write(client, 0x07, 0xfe );
	lt8911exb_write(client, 0x07, 0xff );
	lt8911exb_write(client, 0x0a, 0xfc );
	lt8911exb_write(client, 0x0a, 0xfe );

//-----------------------------------------//

#ifdef _EDP_Pattern_                        // test pattern Display

	lt8911exb_write(client, 0xff, 0xa8 );  // register bank
	lt8911exb_write(client, 0x2d, 0x88 );

#else

	lt8911exb_write(client, 0xff, 0xa8 );  // register bank

#ifdef _Msa_Active_Only_
	lt8911exb_write(client, 0x2d, 0x80 );
#else
	lt8911exb_write(client, 0x2d, 0x00 );
#endif

#endif
//-----------------------------------------//

	/* link train */
	lt8911exb_write(client, 0xff, 0x85 );  //register bank
	lt8911exb_write(client, 0x1a, _eDP_Lane_ );
//	lt8911exb_write(client,0x16,0x02);
//	lt8911exb_write(client,0x13,0xd1);

#ifdef _link_train_enable_
	lt8911exb_write(client, 0xff, 0xac );  //register bank
	lt8911exb_write(client, 0x00, 0x64 );
	lt8911exb_write(client, 0x01, 0x0a );
	lt8911exb_write(client, 0x0c, 0x85 );
	lt8911exb_write(client, 0x0c, 0xc5 );
#else
	lt8911exb_write(client, 0xff, 0xac );
	lt8911exb_write(client, 0x00, 0x00 );
	lt8911exb_write(client, 0x01, 0x0a );
	lt8911exb_write(client, 0x14, 0x80 );
	lt8911exb_write(client, 0x14, 0x81 );
	mdelay(50);
	lt8911exb_write(client, 0x14, 0x84 );
	mdelay(50);
	lt8911exb_write(client, 0x14, 0xc0 );

#endif

}

/***********************************************************/

/*
   reg 0x822/0x8226 : bit1 bit0
   Da_edptx0_tap0_current_tune software output set value:

   2'b00 = None;
   2'b01 = 12.8mA;
   2'b10 = 15mA

   //--------------------------//

   reg 0x8223/ 0x8227 bit7 ~ bit0
   Da_edptx_tap0_current_tune software output set value:

   8'b00000000 = None;
   8'b00000001 = 50uA;
   8'b00000010 = 100uA;
   8'b00000100 = 200uA;
   8'b00001000 = 400uA;
   8'b00010000 = 800uA;
   8'b00100000 = 1.6mA;
   8'b01000000 = 3.2mA;
   8'b10000000 = 6.4mA

   //--------------------------//

 */
enum
{
	_Level0_ = 0,                                           // 27.8 mA  0x83/0x00
	_Level1_,                                               // 26.2 mA  0x82/0xe0
	_Level2_,                                               // 24.6 mA  0x82/0xc0
	_Level3_,                                               // 23 mA    0x82/0xa0
	_Level4_,                                               // 21.4 mA  0x82/0x80
	_Level5_,                                               // 18.2 mA  0x82/0x40
	_Level6_,                                               // 16.6 mA  0x82/0x20
	_Level7_,                                               // 15mA     0x82/0x00
	_Level8_,                                               // 12.8mA   0x81/0x00
	_Level9_,                                               // 11.2mA   0x80/0xe0
	_Level10_,                                              // 9.6mA    0x80/0xc0
	_Level11_,                                              // 8mA      0x80/0xa0
	_Level12_,                                              // 6mA      0x80/0x80
};

u8	Swing_Setting1[] = { 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, 0x80, 0x80, 0x80, 0x80 };
u8	Swing_Setting2[] = { 0x00, 0xe0, 0xc0, 0xa0, 0x80, 0x40, 0x20, 0x00, 0x00, 0xe0, 0xc0, 0xa0, 0x80 };

u8	Level = _Level0_;                                       // normal


/***********************************************************

***********************************************************/
void LT8911EX_TxSwingPreSet(struct i2c_client *client)
{
	lt8911exb_write(client, 0xFF, 0x82 );                  // register bank
	lt8911exb_write(client, 0x22, Swing_Setting1[Level] ); //lane 0 tap0
	lt8911exb_write(client, 0x23, Swing_Setting2[Level] );
	lt8911exb_write(client, 0x24, 0x80 );                  //lane 0 tap1
	lt8911exb_write(client, 0x25, 0x00 );

	#if( _eDP_Lane_>= 2)
		{
		lt8911exb_write(client, 0x26, Swing_Setting1[Level] ); //lane 1 tap0
		lt8911exb_write(client, 0x27, Swing_Setting2[Level] );
		lt8911exb_write(client, 0x28, 0x80 );                  //lane 1 tap1
		lt8911exb_write(client, 0x29, 0x00 );
		}

	#if( _eDP_Lane_ == 4) // 4 lane eDP
		{
		lt8911exb_write(client, 0x2a, Swing_Setting1[Level] ); //lane 2 tap0
		lt8911exb_write(client, 0x2b, Swing_Setting2[Level] );
		lt8911exb_write(client, 0x2c, 0x80 );                  //lane 2 tap1
		lt8911exb_write(client, 0x2d, 0x00 );
		lt8911exb_write(client, 0x2e, Swing_Setting1[Level] ); //lane 3 tap0
		lt8911exb_write(client, 0x2f, Swing_Setting2[Level] );
		lt8911exb_write(client, 0x30, 0x80 );                  //lane 3 tap1
		lt8911exb_write(client, 0x31, 0x00 );
		}
	#endif
	#endif
}



void lt8911exb_config(struct i2c_client *client)
{
    lt8911exb_edp_video_cfg(client);
    //lt8911exb_mipi_video_timing(client); //defualt setting is 1080P
    lt8911exb_setup(client);

    lt8911exb_video_check(client); //just for Check MIPI Input

    lt8911exb_write(client, 0xff, 0x81 ); // register bank
	lt8911exb_write(client, 0x06, 0xdf );
	mdelay(10);
	lt8911exb_write(client, 0x06, 0xff );

    LT8911EX_TxSwingPreSet(client);
    lt8911exb_link_train(client);
}

static int lt8911exb_probe(struct i2c_client * client, const struct i2c_device_id * id)
{
    int ret = -1;
    struct lt8911exb_data *lt8911exb;

    /* do NOT remove these logs */
    dev_info(&client->dev, "LT8911EXB Driver Version: %s\n", LT8911EXB_DRIVER_VERSION);
    dev_info(&client->dev, "LT8911EXB I2C Address: 0x%02x\n", client->addr);

    if(!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
        dev_err(&client->dev, "Failed check I2C functionality");
        return -ENODEV;
    }

    lt8911exb = devm_kzalloc(&client->dev, sizeof(*lt8911exb), GFP_KERNEL);

    if(lt8911exb == NULL) {
        dev_err(&client->dev, "Failed alloc lt8911exb memory");
        return -ENOMEM;
    }

    if(client->dev.of_node) {
        ret = lt8911exb_parse_dt(&client->dev, lt8911exb);

        if(ret < 0) {
            dev_err(&client->dev, "Failed parse dlt8911exb\n");
            goto exit_free_client_data;
        }
    }

    lt8911exb->client = client;
    i2c_set_clientdata(client, lt8911exb);

    ret = lt8911exb_request_io_port(lt8911exb);

    if(ret < 0) {
        dev_err(&client->dev, "Failed request IO port\n");
        goto exit_free_client_data;
    }

    lt8911exb_reset_guitar(client);

    ret = lt8911exb_i2c_test(client);

    if(ret < 0) {
        dev_err(&client->dev, "Failed communicate with IC use I2C\n");
        goto exit_free_io_port;
    }

    lt8911exb_config(client);
    dev_info(&client->dev, "LT8911EXB setup finish.\n");

    return 0;

exit_free_io_port:

    if(gpio_is_valid(lt8911exb->rst_gpio))
        gpio_free(lt8911exb->rst_gpio);

    if(gpio_is_valid(lt8911exb->pwr_gpio))
        gpio_free(lt8911exb->pwr_gpio);

exit_free_client_data:
    devm_kfree(&client->dev, lt8911exb);
    i2c_set_clientdata(client, NULL);

    return ret;
}

static int lt8911exb_remove(struct i2c_client * client)
{
    struct lt8911exb_data *lt8911exb = i2c_get_clientdata(client);

    if(gpio_is_valid(lt8911exb->rst_gpio))
        gpio_free(lt8911exb->rst_gpio);

    if(gpio_is_valid(lt8911exb->pwr_gpio))
        gpio_free(lt8911exb->pwr_gpio);

    dev_info(&client->dev, "goodix lt8911exb driver removed");
    i2c_set_clientdata(client, NULL);

    devm_kfree(&client->dev, lt8911exb);

    return 0;
}

static const struct of_device_id lt8911exb_match_table[] = {
    {.compatible = "lontium,lt8911exb",},
    { },
};

static const struct i2c_device_id lt8911exb_device_id[] = {
    { LT8911EXB_I2C_NAME, 0 },
    { }
};

static struct i2c_driver lt8911exb_driver = {
    .probe      = lt8911exb_probe,
    .remove     = lt8911exb_remove,
    .id_table   = lt8911exb_device_id,
    .driver = {
        .name     = LT8911EXB_I2C_NAME,
        .owner    = THIS_MODULE,
        .of_match_table = lt8911exb_match_table,
    },
};

static int __init lt8911exb_init(void)
{
    s32 ret;

    pr_info("Lontium LT8911EXB driver installing....\n");
    ret = i2c_add_driver(&lt8911exb_driver);

    return ret;
}

static void __exit lt8911exb_exit(void)
{
    pr_info("Lontium LT8911EXB driver exited\n");
    i2c_del_driver(&lt8911exb_driver);
}

fs_initcall(lt8911exb_init);
module_exit(lt8911exb_exit);

MODULE_DESCRIPTION("Lontium LT8911EXB Driver");
MODULE_LICENSE("GPL V2");

